package com.tool.xmltool.handler;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.tool.xmltool.cache.XmlConfigCache;
import com.tool.xmltool.entry.ComXmlConfigDO;
import com.tool.xmltool.entry.ConfigStore;
import com.tool.xmltool.entry.XmlMessage;
import com.tool.xmltool.exception.XmlToolException;
import com.tool.xmltool.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.*;

import java.util.*;

import static com.tool.xmltool.constant.XmlConstant.CYCLIC_FIRST;
import static com.tool.xmltool.constant.XmlConstant.REMOVE_BRACKETS;

/**
 * 将xml转为map.
 *
 * @author: zhoubin
 */
@Slf4j
public class XmlUnPackHandler {

    /**
     * xml报文体
     */
    private String xmlMsg;

    /**
     * 是否校验报文字段
     */
    private boolean checkNode;

    /**
     * 报文整体
     */
    private XmlMessage xmlMessage;

    /**
     * 报文配置存储对象
     */
    ConfigStore configStore;

    public XmlUnPackHandler(ConfigStore configStore) {
        this.configStore = configStore;
    }

    public void initData(String msg) {
        initData(msg, false);
    }

    /**
     * 初始化内容
     *
     * @param msg xml内容
     * @param checkNode 是否校验每个xml节点
     */
    public void initData(String msg,  boolean checkNode) {
        this.xmlMsg = msg;
        this.checkNode = checkNode;
    }

    /**
     * 解析报文体
     *
     * @return
     */
    public XmlMessage parseXml(){

        // 判断是否为空
        if (Objects.nonNull(xmlMessage)) {
            return xmlMessage;
        }

        // 若不为空，则需要解一次xml
        xmlMessage = new XmlMessage();

        Map<String, Object> bodyMap = new LinkedHashMap<>(121);
        try {
            String errNode = xml2Map(xmlMsg, bodyMap);

            if (CharSequenceUtil.isNotBlank(errNode)) {
                errNode =  errNode + " invalid";
            }

            if (CharSequenceUtil.isNotBlank(errNode)) {
                xmlMessage.errMsg(errNode);
            }

            xmlMessage.setBodyMap(bodyMap);

        } catch (Exception e) {
            throw new XmlToolException("S2004","xml解析失败: ",e);
        }

        return xmlMessage;
    }

    /**
     * xml转换Map
     *
     * @param message - xmlString
     * @param bodyMap - 报文体内容
     * @return Map
     */
    private String xml2Map(String message, Map<String, Object> bodyMap) throws DocumentException {

        Document doc = null;
        message = replaceStr(message);
        try {
            doc = DocumentHelper.parseText(message);
        } catch (DocumentException e) {
            throw e;
        }
        Element root = doc.getRootElement();
        String errNode = element2Map(root, bodyMap, configStore.getConfigMap());

        return errNode;
    }

    /**
     * Emelent转换成map
     *
     * @param node - element
     * @param data - 返回数据
     * @param xmlConfigMap - 节点路径与xml配置对象的对应关系
     */
    private String element2Map(Element node, Map<String, Object> data, Map<String, ComXmlConfigDO> xmlConfigMap) {

        String errNode = null;

        if (node == null) {
            return null;
        }
        // 当报文节点没有子节点时
        if (node.elements().size() == 0) {
            String uniquePath = node.getUniquePath();
            ComXmlConfigDO comXmlConfigDO = findByXpath(uniquePath, xmlConfigMap);
            String nodeText = node.getText().trim();
            String relTxt = nodeText;

            if (comXmlConfigDO == null) {
                String path = uniquePath.replaceAll(REMOVE_BRACKETS, "");
                String relNodePath = path + relTxt.substring(0, 4);
                comXmlConfigDO = findByXpath(relNodePath, xmlConfigMap);
                relTxt = relTxt.substring(5);
            }

            // 正则表达式校验
            if (checkNode && !checkNode(relTxt, comXmlConfigDO)) {
                errNode = uniquePath;
            }

            addValueToMap(comXmlConfigDO, data, relTxt);
        }

        //当前节点的所有属性的list
        List<Attribute> listAttr = node.attributes();
        //遍历当前节点的所有属性
        for (Attribute attr : listAttr) {
            ComXmlConfigDO byXpathConfig = findByXpath(attr.getUniquePath(), xmlConfigMap);

            String attrValue = attr.getValue().trim();

            // 正则表达式校验
            if (checkNode && !checkNode(attrValue, byXpathConfig)) {
                errNode = attr.getUniquePath();
            }

            addValueToMap(byXpathConfig, data, attrValue);
        }

        //递归遍历当前节点所有的子节点
        //所有一级子节点的list
        List<Element> listElement = node.elements();
        // 每个循环域创建一个list
        List<Map<String, Object>> appendList = new ArrayList<>();
        // 遍历所有一级子节点
        for (Element element : listElement) {
            String uniquePath = element.getUniquePath();
            // 根据xpath查找出xml配置对象
            ComXmlConfigDO config = findRelConfig(xmlConfigMap, uniquePath);

            if (checkNode && checkCyclic(uniquePath, config)) {
                errNode = uniquePath.replaceAll(REMOVE_BRACKETS, "");
                break;
            }

            if (config == null || CollUtil.isEmpty(config.getAppendList())) {
                errNode = element2Map(element, data, xmlConfigMap);
            } else {
                // 如果该节点是循环节点, 则需要将值存入map的循环节点下的list中
                if (Objects.isNull(data.get(config.getBusinessSemantics()))) {
                    appendList = new ArrayList<>();
                    data.put(config.getBusinessSemantics(), appendList);
                }
                Map<String, Object> appendMap = new LinkedHashMap<>();
                appendList.add(appendMap);
                errNode = element2Map(element, appendMap, xmlConfigMap);
            }

            // 当有节点格式校验未通过时直接退出
            if (CharSequenceUtil.isNotBlank(errNode)) {
                break;
            }
        }

        return errNode;
    }

    /**
     * 判断报文中的循环节点是否与配置中的匹配
     * @param uniquePath
     * @param config
     * @return
     */
    private boolean checkCyclic(String uniquePath, ComXmlConfigDO config) {

        if (uniquePath.endsWith(CYCLIC_FIRST)) {

            if (Objects.isNull(config) || !config.getCyclicFieldFlag()) {
                return true;
            }
        }

        return false;
    }

    /**
     * 根据xpath查找xml配置对象，如果循环域下面没有子节点，则返回循环域对象。
     * @param xmlConfigMap xml配置对象map
     * @param uniquePath 需要查找的xpath
     * @return
     */
    private ComXmlConfigDO findRelConfig(Map<String, ComXmlConfigDO> xmlConfigMap, String uniquePath) {
        ComXmlConfigDO config = findByXpath(uniquePath, xmlConfigMap);

        if (config != null && Objects.equals(config.getFatherPath(), config.getNodePath())) {
            // 判断配置对象的父节点路径与自己的xpath是否相同，如果相同，则找出父节点的配置对象
            config = findFatherConfig(configStore.getConfigList(), config.getNodePath());
        }
        return config;
    }

    /**
     * 查找对应xpath的父节点
     * @param configList xml对象的list
     * @param xpath 需要查找父节点的xpath
     * @return
     */
    private ComXmlConfigDO findFatherConfig(List<ComXmlConfigDO> configList, String xpath) {
        for (ComXmlConfigDO config : configList) {
            if (CollUtil.isNotEmpty(config.getAppendList())) {
                if (Objects.equals(config.getNodePath(), xpath)) {
                    return config;
                }
                ComXmlConfigDO fatherConfig = findFatherConfig(config.getAppendList(), xpath);
                if (fatherConfig != null) {
                    return fatherConfig;
                }
            }
        }
        return null;
    }

    /**
     * 将值保存入map中
     * @param comXmlConfigDO
     * @param data
     * @param nodeText
     */
    private void addValueToMap(ComXmlConfigDO comXmlConfigDO, Map<String, Object> data, String nodeText) {
        if (comXmlConfigDO != null && CharSequenceUtil.isNotBlank(nodeText)) {
            String semantics = comXmlConfigDO.getBusinessSemantics();
            // 如果没有子节点点，则直接存入map中
            data.put(semantics, nodeText);
        }
    }

    /**
     * 根据xpath查找对应的xml配置对象，如果父节点与子节点的xpath相同时，查找出来的是父节点
     * @param verifyPath 要查找的路径
     * @param bodyMap map key为xpath，值为xml配置对象
     * @return
     */
    private ComXmlConfigDO findByXpath(String verifyPath, Map<String, ComXmlConfigDO> bodyMap) {
        verifyPath = verifyPath.replaceAll(REMOVE_BRACKETS, "");

        // 通过map的key查找
        ComXmlConfigDO configDO = bodyMap.get(verifyPath);
        if (configDO != null) {
            return configDO;
        }

        Object[] objects = bodyMap.values().toArray();
        return findCyclic(objects, verifyPath);
    }

    /**
     * 查询TPayXmlConfigDO数组的append中是否有该路径,保证查出的节点是配置文件中最后一层节点
     * @param confArr TPayXmlConfigDO数组
     * @param verifyPath 要查找的路径
     * @return
     */
    private ComXmlConfigDO findCyclic(Object[] confArr, String verifyPath) {
        for (int i = 0, size = confArr.length; i < size; i++) {
            ComXmlConfigDO xmlConfigDO = (ComXmlConfigDO) confArr[i];
            // 获取append
            Map<String, ComXmlConfigDO> append = xmlConfigDO.getAppend();
            // 当append的key中含有该路径时直接返回
            if (CollUtil.isNotEmpty(append) && append.containsKey(verifyPath)) {
                ComXmlConfigDO comXmlConfigDO = append.get(verifyPath);

                // 虽然获取到了一个想要的对象，但是可能该对象下还有一个与它路径相同的对象
                Map<String, ComXmlConfigDO> endAppend = comXmlConfigDO.getAppend();
                if (CollUtil.isNotEmpty(endAppend)) {
                    if (endAppend.containsKey(verifyPath)) {
                        comXmlConfigDO = endAppend.get(verifyPath);
                    }
                }
                return comXmlConfigDO;
            }
            // 当append的key中不含有该路径时且append不为空时，遍历append
            if (CollUtil.isNotEmpty(append)) {
                if (Objects.equals(xmlConfigDO.getNodePath(), verifyPath)) {
                    return xmlConfigDO;
                }
                Object[] appendConfArr = append.values().toArray();
                ComXmlConfigDO configDO = findCyclic(appendConfArr, verifyPath);
                if (configDO != null) {
                    return configDO;
                }
            }
        }
        return null;
    }

    /**
     * 正则表达式判断传入内容是否符合标准
     * @param relTxt 节点中的内容
     * @param xmlConfig 报文节点配置
     * @return 布尔值
     */
    private boolean checkNode(String relTxt, ComXmlConfigDO xmlConfig) {

        if (Objects.isNull(xmlConfig)) {
            return true;
        }

        return StrUtil.regexCheck(relTxt, xmlConfig.getRegex());
    }

    /**
     * 替换字符串
     * 去掉Document节点的命名空间属性
     *
     * @param msg - 需要替换的字符串
     * @return - String 替换后字符串
     */
    private String replaceStr(String msg) {
        String a = "<Document";
        int i = msg.indexOf(a) + a.length();
        String first = msg.substring(0, i);
        String second = msg.substring(i);
        second = second.substring(second.indexOf(">"));
        return first + second;
    }
}
